package com.easy.mongodb.core.toolkit;

import cn.hutool.core.util.StrUtil;
import com.easy.mongodb.common.annotation.*;
import com.easy.mongodb.common.enums.FieldType;
import com.easy.mongodb.common.params.DefaultNestedClass;
import com.easy.mongodb.common.utils.ClassUtils;
import com.easy.mongodb.common.utils.ReflectionKit;
import com.easy.mongodb.common.utils.StringUtils;
import com.easy.mongodb.core.biz.JoinConditionDescription;
import com.easy.mongodb.core.biz.TableFieldInfo;
import com.easy.mongodb.core.biz.TableInfo;
import com.easy.mongodb.core.cache.GlobalConfigCache;
import com.easy.mongodb.core.config.GlobalConfig;
import com.mongodb.client.model.IndexModel;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;

import java.lang.reflect.Field;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static com.easy.mongodb.common.constants.BaseMongoConstants.DEFAULT_ID_NAME;
import static com.easy.mongodb.common.constants.BaseMongoConstants.DEFAULT_MONGO_ID_NAME;


/**
 * @ProjectName: easy-mongodb
 * @Description: 实体字段信息工具类
 * @Author: vapeshop
 * @Date: 2022/6/17 16:49:15
 * @UpdateUser: vapeshop
 * @UpdateDate: 2022/6/17 16:49:15
 * @UpdateRemark: The modified content
 * @Version: 1.0
 * <p>
 * Copyright © 2022 vapeshop Technologies Inc. All Rights Reserved
 **/
public class TableInfoHelper {
    /**
     * 储存反射类表信息
     */
    private static final Map<Class<?>, TableInfo> ENTITY_INFO_CACHE = new ConcurrentHashMap<>();


    /**
     * 获取实体映射表信息
     *
     * @param clazz 类
     * @return 实体字段信息
     */
    public static TableInfo getTableInfo(Class<?> clazz) {
        if (clazz == null) {
            return null;
        }
        TableInfo tableInfo = ENTITY_INFO_CACHE.get(ClassUtils.getUserClass(clazz));
        if (null != tableInfo) {
            return tableInfo;
        }
        // 尝试获取父类缓存
        Class currentClass = clazz;
        while (null == tableInfo && Object.class != currentClass) {
            currentClass = currentClass.getSuperclass();
            tableInfo = ENTITY_INFO_CACHE.get(ClassUtils.getUserClass(currentClass));
        }
        if (tableInfo != null) {
            ENTITY_INFO_CACHE.put(ClassUtils.getUserClass(clazz), tableInfo);
        }

        // 缓存中未获取到,则初始化
        GlobalConfig globalConfig = GlobalConfigCache.getGlobalConfig();
        return initTableInfo(globalConfig, clazz);
    }

    /**
     * 实体类反射获取表信息 初始化
     *
     * @param globalConfig 全局配置
     * @param clazz        类
     * @return 实体信息
     */
    public synchronized static TableInfo initTableInfo(GlobalConfig globalConfig, Class<?> clazz) {
        TableInfo tableInfo = ENTITY_INFO_CACHE.get(clazz);
        if (tableInfo != null) {
            return tableInfo;
        }

        // 没有获取到缓存信息,则初始化
        tableInfo = new TableInfo();
        // 初始化表名相关
        initTableName(clazz, globalConfig, tableInfo);
        // 初始化字段相关
        initTableFields(clazz, globalConfig, tableInfo);
        // 初始化索引相关
        initTableIndexs(clazz, globalConfig, tableInfo);
        // 放入缓存
        ENTITY_INFO_CACHE.put(clazz, tableInfo);
        return tableInfo;
    }

    /**
     * 预添加codecProvider解析object时对非实体类字段的处理(比如自定义字段名,下划线等)
     *
     * @param tableInfo 实体信息
     */
    private static void addExtraProcessor(TableInfo tableInfo, Class<?> clazz) {
        System.out.println(tableInfo);
        Map<String, String> columnMappingMap = tableInfo.getColumnMappingMap();
        Map<Class<?>, Map<String, String>> nestedClassColumnMappingMap = tableInfo.getNestedClassColumnMappingMap();
        JsonWriterSettings settings = JsonWriterSettings.builder().outputMode(JsonMode.RELAXED)
                .objectIdConverter((value, writer) -> writer.writeString(value.toHexString()))
                .dateTimeConverter(
                        (value, writer) -> {
                            ZonedDateTime zonedDateTime = Instant.ofEpochMilli(value).atZone(ZoneOffset.UTC);
                            writer.writeString(DateTimeFormatter.ISO_DATE_TIME.format(zonedDateTime));
                        })

                .build();
//                (object, key, value) -> {
//            // 主类
//            Map<String, String> nestedColumnMappingMap = nestedClassColumnMappingMap.get(object.getClass());
//            if (nestedColumnMappingMap != null) {
//            } else {
//                // 嵌套类
//            }
//        };
//        tableInfo.setPojoCodecProvider(codecProvider);
    }

    /**
     * 初始化 表主键,表字段,索引
     *
     * @param clazz        类
     * @param globalConfig 全局配置
     * @param tableInfo    实体信息
     */
    public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
        // 数据库全局配置
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        List<Field> list = getAllFields(clazz);
        // 标记是否读取到主键
        boolean isReadPK = false;
        // 是否存在 @TableId 注解
        boolean existTableId = isExistTableId(list);

        List<TableFieldInfo> fieldList = new ArrayList<>();
        for (Field field : list) {
            // 主键ID 初始化
            if (!isReadPK) {
                if (existTableId) {
                    isReadPK = initTableIdWithAnnotation(dbConfig, tableInfo, field);
                } else {
                    isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field);
                }
                if (isReadPK) {
                    continue;
                }
            }

            // 有 @TableField 等已知自定义注解的字段初始化
            if (initTableFieldWithAnnotation(dbConfig, fieldList, field, tableInfo)) {
                continue;
            }

            // 无 @TableField 等已知自定义注解的字段初始化
            initTableFieldWithoutAnnotation(dbConfig, fieldList, field, tableInfo);
        }

        // 字段列表
        tableInfo.setFieldList(fieldList);
        // 添加PojoCoder ExtraProcessor
//        addExtraProcessor(tableInfo);
    }


    /**
     * 初始化 表主键,表字段,索引
     *
     * @param clazz        类
     * @param globalConfig 全局配置
     * @param tableInfo    实体信息
     */
    public static void initTableIndexs(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
        // 数据库全局配置
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        Table table = clazz.getAnnotation(Table.class);
        if (table != null) {
            if (table.indexs() != null && table.indexs().length > 0) {
                for (Index index : table.indexs()) {
                    if (index.fields() != null && index.fields().length > 0) {
                        IndexOptions indexOptions = new IndexOptions();
                        indexOptions.name(index.name());
                        indexOptions.unique(index.unique());
                        indexOptions.background(index.background());
                        indexOptions.sparse(index.sparse());
                        if (index.expireAfterSeconds() > 0) {
                            indexOptions.expireAfter(Long.valueOf(index.expireAfterSeconds()), TimeUnit.SECONDS);
                        }
                        IndexModel indexModel;
                        switch (index.type()) {
                            case HASH:
                                indexModel = new IndexModel(Indexes.hashed(tableInfo.getMappingColumn(index.fields()[0].name())), indexOptions);
                                break;
                            case TEXT:
                                indexModel = new IndexModel(Indexes.text(tableInfo.getMappingColumn(index.fields()[0].name())), indexOptions);
                                break;
                            case GEO2D:
                                indexModel = new IndexModel(Indexes.geo2d(tableInfo.getMappingColumn(index.fields()[0].name())), indexOptions);
                                break;
                            case GEO2D_SPHERE:
                                indexModel = new IndexModel(Indexes.geo2dsphere(tableInfo.getMappingColumn(index.fields()[0].name())), indexOptions);
                                break;
                            case COMPOUND:
                            default:
                                List<Bson> keys = new ArrayList<>();
                                Arrays.stream(index.fields()).forEach(
                                        indexField -> {
                                            keys.add(new Document(tableInfo.getMappingColumn(indexField.name()), indexField.direction().reverseMul()));
                                        }
                                );
                                indexModel = new IndexModel(Indexes.compoundIndex(keys), indexOptions);
                                break;
                        }
                        if (StringUtils.isBlank(indexModel.getOptions().getName())) {
                            indexModel.getOptions().name(IndexUtils.createIndexName(indexModel.getKeys().toBsonDocument()));
                        }
                        tableInfo.getIndexOps().put(indexModel.getOptions().getName(), indexModel);
                    }

                }

            }
        }
    }

    /**
     * 字段属性初始化
     *
     * @param dbConfig  索引配置
     * @param fieldList 字段列表
     * @param field     字段
     * @param tableInfo 实体信息
     * @return
     */
    private static boolean initTableFieldWithAnnotation(GlobalConfig.DbConfig
                                                                dbConfig, List<TableFieldInfo> fieldList,
                                                        Field field, TableInfo tableInfo) {
        boolean hasAnnotation = false;

        // 初始化封装TableField注解信息
        if (field.isAnnotationPresent(TableField.class)) {
            initTableFieldAnnotation(dbConfig, tableInfo, field, fieldList);
            hasAnnotation = true;
        }

        return hasAnnotation;
    }


    /**
     * TableField注解信息初始化
     *
     * @param dbConfig  集合配置
     * @param fieldList 字段列表
     * @param field     字段
     * @param tableInfo 实体信息
     */
    private static void initTableFieldAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,
                                                 Field field, List<TableFieldInfo> fieldList) {
        TableField tableField = field.getAnnotation(TableField.class);
        if (tableField.exist()) {
            // 存在字段处理
            TableFieldInfo tableFieldInfo = new TableFieldInfo(dbConfig, field, tableField);
            // 自定义字段名及驼峰下划线转换
            String mappingColumn;
            if (!StrUtil.isBlank(tableField.value().trim())) {
                // 自定义注解指定的名称优先级最高
                tableInfo.getMappingColumnMap().putIfAbsent(field.getName(), tableField.value());
                tableInfo.getColumnMappingMap().putIfAbsent(tableField.value(), field.getName());
                mappingColumn = tableField.value();
            } else {
                // 下划线驼峰
                mappingColumn = initMappingColumnMapAndGet(dbConfig, tableInfo, field);
            }

            // 日期格式化
            if (StrUtil.isNotBlank(tableField.dateFormat())) {
                tableFieldInfo.setDateFormat(tableField.dateFormat());
            }

            // 其它
            tableFieldInfo.setMappingColumn(mappingColumn);
            tableFieldInfo.setFieldType(tableField.fieldType());
            tableFieldInfo.setColumnType(field.getType().getSimpleName());

            // 级联
            if (FieldType.JOIN.equals(tableField.fieldType())) {
                // 追加条件查询字段，用于标识查询数据的
                JoinCondition joinCondition = tableField.condition();
                JoinConditionDescription condition = new JoinConditionDescription(
                        joinCondition.selfField(),
                        joinCondition.joinField(),
                        joinCondition.midEntity(),
                        joinCondition.selfMidField(),
                        joinCondition.joinMidField(),
                        Void.class.equals(joinCondition.entity()) ? field.getType() : joinCondition.entity(),
                        joinCondition.field());
                tableFieldInfo.setDescription(condition);
                //TODO
//                entityFieldInfo.setParentName(tableField.parentName());
//                entityFieldInfo.setChildName(tableField.childName());

//                entityInfo.setJoinFieldName(mappingColumn);
//                entityInfo.setJoinFieldClass(tableField.joinFieldClass());
                Class<?> clazz = tableField.condition().entity();
                if (clazz.equals(Void.class)) {
                    clazz = field.getType();
                }
                tableInfo.getPathClassMap().putIfAbsent(field.getName(), field.getType());
                processNested(clazz, dbConfig, tableInfo);
            }

            fieldList.add(tableFieldInfo);

            // 嵌套类处理
            if (DefaultNestedClass.class != tableField.nestedClass()) {
                // 嵌套类
                tableInfo.getPathClassMap().putIfAbsent(field.getName(), tableField.nestedClass());
                processNested(tableField.nestedClass(), dbConfig, tableInfo);
            }

        } else {
//            entityInfo.getNotSerializeField().add(field.getName());
        }
    }


    /**
     * 处理嵌套类中的字段配置
     *
     * @param nestedClass 嵌套类
     * @param dbConfig    全局配置
     * @param tableInfo   实体信息
     */
    private static void processNested(Class<?> nestedClass, GlobalConfig.DbConfig dbConfig, TableInfo tableInfo) {
        // 将字段映射置入map 对其子节点也执行同样的操作
        List<Field> allFields = getAllFields(nestedClass);
        Map<String, String> mappingColumnMap = new HashMap<>(allFields.size());
        Map<String, String> columnMappingMap = new HashMap<>(allFields.size());
        List<TableFieldInfo> tableFieldInfoList = new ArrayList<>();
        Set<String> notSerializedFields = new HashSet<>();
        allFields.forEach(field -> {
            String mappingColumn;
            // 处理TableField注解
            TableField tableField = field.getAnnotation(TableField.class);
            if (Objects.isNull(tableField)) {
                mappingColumn = getMappingColumn(dbConfig, field);
                TableFieldInfo tableFieldInfo = new TableFieldInfo(dbConfig, field);
                tableFieldInfo.setMappingColumn(mappingColumn);
                tableFieldInfo.setFieldType(FieldType.TEXT);
                tableFieldInfo.setColumnType(FieldType.TEXT.getType());
                tableFieldInfoList.add(tableFieldInfo);
            } else {
                if (tableField.exist()) {
                    // 子嵌套,递归处理
                    if (DefaultNestedClass.class != tableField.nestedClass()) {
                        tableInfo.getPathClassMap().putIfAbsent(field.getName(), tableField.nestedClass());
                        processNested(tableField.nestedClass(), dbConfig, tableInfo);
                    }

                    // 字段名称
                    if (StrUtil.isNotBlank(tableField.value().trim())) {
                        mappingColumn = tableField.value();
                    } else {
                        mappingColumn = getMappingColumn(dbConfig, field);
                    }

                    // 设置实体字段信息
                    TableFieldInfo tableFieldInfo = new TableFieldInfo(dbConfig, field, tableField);
                    tableFieldInfo.setMappingColumn(mappingColumn);
                    FieldType fieldType = FieldType.NESTED.equals(tableField.fieldType()) ? FieldType.NESTED : FieldType.TEXT;
                    tableFieldInfo.setFieldType(fieldType);
                    tableFieldInfo.setColumnType(fieldType.getType());
                    tableFieldInfoList.add(tableFieldInfo);
                } else {
                    mappingColumn = getMappingColumn(dbConfig, field);
                    notSerializedFields.add(field.getName());
                }
            }
            columnMappingMap.putIfAbsent(mappingColumn, field.getName());
            mappingColumnMap.putIfAbsent(field.getName(), mappingColumn);

        });
//        entityInfo.getNestedNotSerializeField().putIfAbsent(nestedClass, notSerializedFields);
        tableInfo.getNestedClassColumnMappingMap().putIfAbsent(nestedClass, columnMappingMap);
        tableInfo.getNestedClassMappingColumnMap().putIfAbsent(nestedClass, mappingColumnMap);
        tableInfo.getNestedFieldListMap().put(nestedClass, tableFieldInfoList);

    }


    /**
     * 字段属性初始化
     *
     * @param dbConfig  索引配置
     * @param fieldList 字段列表
     * @param field     字段
     * @param tableInfo 实体信息
     */
    private static void initTableFieldWithoutAnnotation(GlobalConfig.DbConfig
                                                                dbConfig, List<TableFieldInfo> fieldList,
                                                        Field field, TableInfo tableInfo) {
        TableFieldInfo tableFieldInfo = new TableFieldInfo(dbConfig, field);
        // 初始化
        String mappingColumn = initMappingColumnMapAndGet(dbConfig, tableInfo, field);
        tableFieldInfo.setMappingColumn(mappingColumn);
        tableFieldInfo.setColumnType(field.getType().getSimpleName());
        fieldList.add(tableFieldInfo);
    }


    /**
     * 主键属性初始化
     *
     * @param dbConfig  索引配置
     * @param tableInfo 实体信息
     * @param field     字段
     * @return 布尔值
     */
    private static boolean initTableIdWithAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,
                                                     Field field) {
        TableId tableId = field.getAnnotation(TableId.class);
        if (tableId != null) {
            // 字段
            field.setAccessible(Boolean.TRUE);
            tableInfo.setClazz(field.getDeclaringClass())
                    .setKeyField(field)
                    .setIdClass(field.getType())
                    .setKeyProperty(field.getName());

//            entityInfo.getNotSerializeField().add(DEFAULT_ID_NAME);
//            entityInfo.getNotSerializeField().add(field.getName());
            tableInfo.getMappingColumnMap().putIfAbsent(field.getName(), DEFAULT_ID_NAME);
            return true;
        }
        return false;
    }


    /**
     * 主键属性初始化
     *
     * @param dbConfig  索引配置
     * @param tableInfo 实体信息
     * @param field     字段
     * @return 布尔值
     */
    private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,
                                                        Field field) {
        String column = field.getName();
        if (DEFAULT_ID_NAME.equalsIgnoreCase(column) || DEFAULT_MONGO_ID_NAME.equals(column)) {
            field.setAccessible(Boolean.TRUE);
            tableInfo
                    .setKeyProperty(field.getName())
                    .setKeyField(field)
                    .setIdClass(field.getType())
                    .setClazz(field.getDeclaringClass());
//            entityInfo.getNotSerializeField().add(DEFAULT_ID_NAME);
//            entityInfo.getNotSerializeField().add(field.getName());
            tableInfo.getMappingColumnMap().putIfAbsent(field.getName(), DEFAULT_ID_NAME);
            return true;
        }
        return false;
    }

    /**
     * 判断主键注解是否存在
     *
     * @param list 字段列表
     * @return 布尔值
     */
    public static boolean isExistTableId(List<Field> list) {
        for (Field field : list) {
            TableId tableId = field.getAnnotation(TableId.class);
            if (tableId != null) {
                return true;
            }
        }
        return false;
    }


    /**
     * 获取该类的所有属性列表
     *
     * @param clazz 类
     * @return 字段列表
     */
    public static List<Field> getAllFields(Class<?> clazz) {
        return ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz));
    }

    /**
     * 初始化表(索引)名称
     *
     * @param clazz        类
     * @param globalConfig 全局配置
     * @param tableInfo    实体信息
     */
    private static void initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
        // 数据库全局配置
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        Table table = clazz.getAnnotation(Table.class);
        String tableName = clazz.getSimpleName().toLowerCase(Locale.ROOT);
        String tablePrefix = dbConfig.getTablePrefix();

        boolean tablePrefixEffect = true;
        String collectionName;
        String database = dbConfig.getDatabase();
        if (Objects.isNull(table)) {
            // 无注解, 直接使用类名
            collectionName = tableName;
            database = globalConfig.getDbConfig().getDatabase();
        } else {
            // 有注解,看注解中是否有指定
            if (StrUtil.isNotBlank(table.value())) {
                collectionName = table.value();
                if (StrUtil.isNotBlank(tablePrefix) && !table.keepGlobalPrefix()) {
                    tablePrefixEffect = false;
                }
            } else {
                collectionName = tableName;
            }
            // 有注解,看注解中是否有指定数据源
            if (StrUtil.isNotBlank(table.database().trim())) {
                database = table.database().trim();
            }
//            entityInfo.setChild(table.child());
//            entityInfo.setChildClass(table.childClass());
        }

        String targetCollectionName = collectionName;
        if (StrUtil.isNotBlank(tablePrefix) && tablePrefixEffect) {
            targetCollectionName = tablePrefix + targetCollectionName;
        }
        tableInfo.setCollectionName(targetCollectionName);
        if (StrUtil.isNotBlank(database)) {
            database = globalConfig.getDbConfig().getDatabase();
        }
        tableInfo.setDatabase(database);
    }

    /**
     * 初始化实体类字段与mongo字段映射关系Map
     *
     * @param dbConfig  配置信息
     * @param tableInfo 实体信息
     * @param field     字段
     * @return mappingColumn es中的字段名
     */
    private static String initMappingColumnMapAndGet(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field
            field) {
        // 自定义字段名及驼峰下划线转换
        String mappingColumn = getMappingColumn(dbConfig, field);
        tableInfo.getMappingColumnMap().putIfAbsent(field.getName(), mappingColumn);
        return mappingColumn;
    }

    /**
     * 获取实体类字段对应的mongo字段
     *
     * @param dbConfig 全局配置
     * @param field    字段
     * @return es字段名
     */
    private static String getMappingColumn(GlobalConfig.DbConfig dbConfig, Field field) {
        String mappingColumn = field.getName();
        if (dbConfig.isMapUnderscoreToCamelCase()) {
            // 下划线转驼峰
            mappingColumn = StrUtil.toUnderlineCase(field.getName());
        }
        return mappingColumn;
    }
}
